SuiteQL Query Tool
Learning Edition Guide

Welcome to the Learning Edition of the SuiteQL Query Tool. This guide will help you navigate the codebase and understand the key SuiteScript concepts demonstrated throughout the application.

Table of Contents

Quick Start: Your First 5 Minutes

Want to see something working right away? Here's the fastest path to understanding how this tool works.

Step 1: Run Your First Query

Open the SuiteQL Query Tool and paste this simple query into the editor:

SELECT
    ID,
    CompanyName,
    Email
FROM
    Customer
WHERE
    ROWNUM <= 10

Click Run (or press Ctrl+Enter). You should see 10 customer records appear in the results.

Step 2: Understand What Happened

Here's what just occurred behind the scenes:

  1. Your browser sent a POST request to the Suitelet with the query text
  2. The Suitelet's handlePostRequest() function received it
  3. It called executeQuery() which used query.runSuiteQL() to execute your SQL
  4. NetSuite returned the data, which was sent back as JSON
  5. The browser JavaScript rendered the results in the table

Step 3: Find This in the Code

Open suiteql-query-tool.v2026.01.suitelet-learning.js and search for:

Step 4: Try Modifying

Now experiment! Try these modifications:

You're Ready! You've just executed a SuiteQL query and traced it through the code. Now you can explore the rest of this guide to understand the deeper concepts, or jump straight to the Key Patterns section.

Purpose of the Learning Edition

The Learning Edition is a fully-annotated version of the SuiteQL Query Tool, designed to help developers learn SuiteScript development through a real-world, production-quality application.

Who Is This For?

What You'll Learn

Tip: Look for comment blocks that start with SUITESCRIPT LEARNING NOTES in the code. These contain detailed explanations of NetSuite-specific concepts.

SuiteScript Primer

Before diving into the code, here's a quick overview of SuiteScript fundamentals.

What is SuiteScript?

SuiteScript is NetSuite's server-side JavaScript API. It allows you to customize and extend NetSuite's functionality through scripts that run on NetSuite's servers. SuiteScript 2.x (the current version) uses the AMD module pattern and supports modern JavaScript features.

Script Types

NetSuite has several script types, each designed for specific use cases:

Script Type Purpose Entry Point(s)
Suitelet Custom pages and API endpoints onRequest
User Event Triggers on record events beforeLoad, beforeSubmit, afterSubmit
Client Script Browser-side form interactions pageInit, saveRecord, fieldChanged, etc.
Scheduled Background processing on a schedule execute
Map/Reduce Large-scale data processing getInputData, map, reduce, summarize
RESTlet REST API endpoints get, post, put, delete

The SuiteQL Query Tool is a Suitelet, which makes it perfect for building custom web applications inside NetSuite.

The N/ Module System

SuiteScript functionality is organized into modules prefixed with "N/". You import these modules using the AMD define() function:

define([
    'N/query',      // SuiteQL queries
    'N/file',       // File Cabinet operations
    'N/runtime'     // Script/user/session info
], function(query, file, runtime) {
    // Your code here
    return {
        onRequest: myFunction
    };
});

Governance

Every SuiteScript operation consumes "governance units." Each script type has a limit:

Script Type Governance Limit
Suitelet 1,000 units
User Event 1,000 units
Client Script 1,000 units
Scheduled Script 10,000 units
Map/Reduce 10,000 units per phase
RESTlet 5,000 units
Common Governance Costs:
query.runSuiteQL() = 10 units
file.load() = 10 units
file.save() = 10 units
https.post() = 10 units
record.load() = 5-10 units
log.debug() = 0 units

Application Architecture

The SuiteQL Query Tool follows a client-server architecture where a single Suitelet serves both the HTML user interface and a JSON API.

Request Flow Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                         USER'S BROWSER                               │
└─────────────────────────────────────────────────────────────────────┘
        │                                          ▲
        │ 1. GET request                           │ 2. HTML page with
        │    (initial page load)                   │    embedded JavaScript
        ▼                                          │
┌─────────────────────────────────────────────────────────────────────┐
│                           SUITELET                                   │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  onRequest(context)                                          │    │
│  │    ├── GET  → handleGetRequest()  → serverWidget form        │    │
│  │    └── POST → handlePostRequest() → JSON response            │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
        ▲                                          │
        │ 3. AJAX POST                             │ 4. JSON data
        │    { function: 'queryExecute', ... }     │    { records: [...] }
        │                                          ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    BROWSER JAVASCRIPT (SQT)                          │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐     │
│  │ runQuery()      │  │ loadSqlFile()   │  │ generateAI()    │     │
│  │ → fetch() POST  │  │ → fetch() POST  │  │ → fetch() POST  │     │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘     │
└─────────────────────────────────────────────────────────────────────┘

Key Architectural Concepts

1. Dual-Purpose Suitelet

The Suitelet handles two types of requests:

2. INLINEHTML for Custom UI

Instead of using NetSuite's built-in form widgets, we inject a complete custom HTML application using the INLINEHTML field type. This gives us full control over the user interface.

3. Client-Side JavaScript

The embedded JavaScript runs in the browser (not as SuiteScript). It uses standard web APIs like fetch() to communicate with the server. This is regular JavaScript with access to the DOM, localStorage, and third-party libraries.

4. Handler Dispatch Pattern

POST requests include a function property that tells the server which operation to perform. The server uses a dispatch table to route requests:

const handlers = {
    'queryExecute': () => executeQuery(context, payload),
    'sqlFileLoad':  () => loadSqlFile(context, payload),
    'sqlFileSave':  () => saveSqlFile(context, payload),
    // ... more handlers
};

const handler = handlers[payload.function];
if (handler) handler();

Code Roadmap

The Learning Edition script is organized into numbered sections. Here's what each section contains:

Section 1: Configuration
Application settings using Object.freeze() for immutable config
Section 2: NetSuite Module Definition
The define() call, module imports, and entry point return
Section 3: Request Handlers
handleGetRequest() and handlePostRequest() - the routing layer
Section 4: Query Execution
SuiteQL execution with N/query, pagination, virtual views
Section 5: File Operations
File Cabinet operations with N/file - load, save, list files
Section 6: Workbooks
NetSuite Workbook integration
Section 6.5: AI Query Generation
External API calls with N/https to AI services
Section 7: Document Generation
PDF generation with N/render and session storage with N/runtime
Section 8: Tables Reference
Secondary page for browsing NetSuite tables
Section 9: HTML Generation
Building the custom UI with HTML, CSS, and client-side JavaScript

Key Patterns to Study

These are the most instructive parts of the codebase for learning SuiteScript:

Beginner Basic Query Execution

Location: Section 4, executeQuery() function

Learn how to execute SuiteQL queries and handle the results:

const records = modules.query.runSuiteQL({
    query: 'SELECT ID, CompanyName FROM Customer WHERE IsInactive = ?',
    params: ['F']  // Parameterized query - prevents SQL injection!
}).asMappedResults();  // Returns array of objects

Beginner File Cabinet Operations

Location: Section 5, loadSqlFile() and saveSqlFile()

Learn how to read and write files in NetSuite's File Cabinet:

// Loading a file
const fileObj = modules.file.load({ id: fileId });
const contents = fileObj.getContents();

// Creating a file
const newFile = modules.file.create({
    name: 'query.sql',
    contents: sqlText,
    folder: folderId,
    fileType: modules.file.Type.PLAINTEXT
});
const savedId = newFile.save();

Intermediate External API Calls

Location: Section 6.5, callAnthropicAPI() and callOpenAIAPI()

Learn how to make HTTPS requests to external services:

const response = modules.https.post({
    url: 'https://api.example.com/endpoint',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + apiKey
    },
    body: JSON.stringify(requestData)
});

if (response.code === 200) {
    const data = JSON.parse(response.body);
}

Intermediate Session Storage

Location: Section 7, submitDocument() and generateDocument()

Learn how to persist data across requests within a user's session:

// Storing data
const session = modules.runtime.getCurrentSession();
session.set({
    name: 'myData',
    value: JSON.stringify(dataObject)
});

// Retrieving data (in a later request)
const stored = session.get({ name: 'myData' });
const data = JSON.parse(stored);

Intermediate PDF Generation

Location: Section 7, generateDocument()

Learn how to generate PDFs from templates with dynamic data:

const renderer = modules.render.create();
renderer.addCustomDataSource({
    alias: 'data',
    format: modules.render.DataSource.OBJECT,
    data: { records: queryResults }
});
renderer.templateContent = xmlTemplate;
const pdfFile = renderer.renderAsPdf();

Advanced Query Pagination

Location: Section 4, executePaginatedQuery()

Learn how to retrieve more than SuiteQL's 5,000 row limit:

// Wrap query with ROWNUM for pagination
const paginatedSql = `
    SELECT * FROM (
        SELECT ROWNUM AS ROWNUMBER, * FROM (${originalQuery})
    ) WHERE ROWNUMBER BETWEEN ${start} AND ${end}
`;

Advanced Custom UI Architecture

Location: Section 9, generateMainHtml() and generateClientScript()

Learn the pattern for building single-page applications inside Suitelets using INLINEHTML, including client-server communication with fetch().

Suggested Learning Path

Here's a recommended order for exploring the codebase:

Phase 1: Understand the Structure

  1. Read the JSDoc decorators at the top of the file
  2. Study the define() call and module imports (Section 2)
  3. Understand the onRequest entry point and how it routes GET vs POST

Phase 2: Learn Core Operations

  1. Study executeQuery() - this is the heart of the application
  2. Explore file operations - loading and saving SQL files
  3. Understand handlePostRequest() and the dispatch pattern

Phase 3: Advanced Patterns

  1. Study external API calls in the AI generation section
  2. Explore PDF generation with the render module
  3. Understand session storage for multi-step processes

Phase 4: UI Architecture

  1. See how INLINEHTML is used to inject custom HTML
  2. Study the client-side JavaScript and its communication with the server
  3. Understand the fetch() pattern for AJAX calls
Study Tip: As you read the code, try modifying small things and deploying to see the effects. Experimentation is the best teacher!

Glossary of NetSuite Terms

NetSuite has its own vocabulary. Here are key terms you'll encounter:

Term Definition
Internal ID A unique numeric identifier assigned to every record in NetSuite. Used in scripts to reference specific records (e.g., Customer ID 12345).
Script ID A human-readable identifier you assign when creating scripts, custom fields, or custom records. Always prefixed (e.g., customscript_my_suitelet, custbody_my_field).
Deployment An instance of a script configured to run. One script can have multiple deployments with different settings, permissions, or triggers.
File Cabinet NetSuite's file storage system. Organized into folders, stores scripts, images, documents, and other files.
Governance NetSuite's resource management system. Each script operation costs "units" and scripts have limits to prevent runaway processes.
Record Type A category of data in NetSuite (e.g., Customer, Sales Order, Invoice). Each has specific fields and behaviors.
Subsidiary A company entity within NetSuite. Used for multi-company or multi-country setups. Data is often filtered by subsidiary.
Role A set of permissions assigned to users. Determines what records and scripts a user can access.
Saved Search NetSuite's original query system (pre-SuiteQL). Still widely used but being superseded by SuiteQL for complex queries.
Workbook NetSuite's visual query builder (Analytics). Can be converted to SuiteQL using query.load().toSuiteQL().
SuiteApp / Bundle A packaged set of customizations (scripts, records, fields) that can be installed as a unit.
Sandbox A copy of your production NetSuite account for testing. Changes here don't affect production.
SDF (SuiteCloud Development Framework) NetSuite's modern development tooling for source control, IDE integration, and deployment automation.
BUILTIN.DF() "Display Field" - A SuiteQL function that returns the display name instead of the internal ID for reference fields.
Custom Record A user-defined record type for storing data that doesn't fit standard records. Tables are named customrecord_xxx.

Deployment Walkthrough

Here's how to deploy the SuiteQL Query Tool (or any Suitelet) to NetSuite:

Step 1: Upload the Script File

  1. Go to Documents > Files > File Cabinet
  2. Navigate to SuiteScripts folder (create it if it doesn't exist)
  3. Click Add File and upload suiteql-query-tool.v2026.01.suitelet.js
  4. Note the file's location path (e.g., SuiteScripts/suiteql-query-tool.v2026.01.suitelet.js)

Step 2: Create the Script Record

  1. Go to Customization > Scripting > Scripts > New
  2. Select the uploaded script file
  3. NetSuite will detect it as a Suitelet and pre-fill the type
  4. Fill in the script record:
  5. Click Save

Step 3: Create a Deployment

  1. On the Script record, go to the Deployments subtab
  2. Click New Deployment
  3. Fill in the deployment:
  4. On the Audience subtab, select which roles can access the Suitelet
  5. Click Save

Step 4: Access the Suitelet

After saving the deployment, you can access the Suitelet via:

URL Format: Suitelet URLs look like:
https://[account-id].app.netsuite.com/app/site/hosting/scriptlet.nl?script=123&deploy=1

The script and deploy parameters are the internal IDs of the script and deployment records.

Step 5: (Optional) Add to Navigation

To add a menu item for easy access:

  1. Go to Customization > Centers and Tabs > Center Links
  2. Create a new link pointing to your Suitelet URL
  3. Assign it to the appropriate center (e.g., Reports, Setup)

Common Errors & Troubleshooting

Here are errors you'll commonly encounter and how to resolve them:

SSS_USAGE_LIMIT_EXCEEDED

Error: "You have exceeded the governance limit for this script."

Cause: Your script used more governance units than its limit allows.

Solutions:

SSS_INVALID_SRCH_COLUMN / Invalid Column

Error: "Invalid search column: [column_name]"

Cause: The column doesn't exist on the table, or you don't have permission to access it.

Solutions:

SSS_INVALID_SRCH_FILTER / Invalid Table

Error: "Invalid table: [table_name]"

Cause: The table doesn't exist or you don't have permission to access it.

Solutions:

UNEXPECTED_ERROR

Error: "An unexpected error occurred."

Cause: A generic error, often from malformed queries or server issues.

Solutions:

Search Timed Out

Error: "Search timed out. Please refine your search criteria."

Cause: The query took too long to execute (usually over 3-5 minutes).

Solutions:

INVALID_API_KEY / Authentication Errors (AI Features)

Error: "Invalid API key" or "401 Unauthorized"

Cause: The AI API key is incorrect, expired, or has insufficient permissions.

Solutions:

File Not Found / Load Error

Error: "File not found" or "That record does not exist."

Cause: Trying to load a file that doesn't exist or was deleted.

Solutions:

SuiteQL Quick Reference

A handy reference for common SuiteQL patterns and syntax.

Common Tables

Table Description Key Columns
Transaction All transactions (orders, invoices, etc.) id, tranid, type, entity, trandate, status, total
TransactionLine Line items on transactions id, transaction, item, quantity, amount, rate
Customer Customer records id, entityid, companyname, email, salesrep
Vendor Vendor records id, entityid, companyname, email
Employee Employee records id, entityid, firstname, lastname, email, department
Item All item types id, itemid, displayname, baseprice, itemtype
Account Chart of accounts id, acctnumber, acctname, accttype
Department Department records id, name, parent, isinactive
Location Location records id, name, parent, isinactive
Subsidiary Subsidiary records id, name, parent, isinactive
File File Cabinet files id, name, folder, filetype, url

Transaction Type Codes

Use these in WHERE Transaction.type = 'xxx':

Code Transaction Type
SalesOrdSales Order
InvoiceInvoice
CustInvcCustomer Invoice
CashSaleCash Sale
CustPymtCustomer Payment
CustCredCredit Memo
PurchOrdPurchase Order
VendBillVendor Bill
VendPymtVendor Payment
VendCredVendor Credit
JournalJournal Entry
CheckCheck
DepositDeposit
TransferTransfer
ItemRcptItem Receipt
ItemShipItem Fulfillment
RtnAuthReturn Authorization
EstimateQuote/Estimate
OpportuntyOpportunity

Useful Functions

-- Get display value instead of internal ID
BUILTIN.DF( Transaction.status ) AS StatusName

-- Handle NULL values
NVL( Customer.phone, 'No Phone' ) AS Phone

-- Date formatting
TO_CHAR( Transaction.trandate, 'YYYY-MM-DD' ) AS FormattedDate

-- Date parsing
TO_DATE( '2024-01-15', 'YYYY-MM-DD' )

-- Current date
SYSDATE

-- Date arithmetic
ADD_MONTHS( SYSDATE, -3 )  -- 3 months ago
TRUNC( SYSDATE )           -- Today at midnight

-- String functions
UPPER( Customer.companyname )
LOWER( Customer.email )
SUBSTR( Item.itemid, 1, 3 )
CONCAT( firstname, CONCAT( ' ', lastname ) )

-- Conditional logic
CASE
    WHEN amount > 1000 THEN 'Large'
    WHEN amount > 100 THEN 'Medium'
    ELSE 'Small'
END AS SizeCategory

-- Aggregates
COUNT( * ), SUM( amount ), AVG( rate ), MIN( trandate ), MAX( trandate )

Common Query Patterns

Basic SELECT with JOIN

SELECT
    Transaction.tranid,
    Transaction.trandate,
    BUILTIN.DF( Transaction.entity ) AS CustomerName,
    Transaction.total
FROM
    Transaction
    INNER JOIN Customer ON Customer.id = Transaction.entity
WHERE
    Transaction.type = 'SalesOrd'
    AND Transaction.trandate >= TO_DATE( '2024-01-01', 'YYYY-MM-DD' )
ORDER BY
    Transaction.trandate DESC

Transaction with Lines

SELECT
    Transaction.tranid,
    TransactionLine.linesequencenumber,
    BUILTIN.DF( TransactionLine.item ) AS ItemName,
    TransactionLine.quantity,
    TransactionLine.rate,
    TransactionLine.amount
FROM
    Transaction
    INNER JOIN TransactionLine ON TransactionLine.transaction = Transaction.id
WHERE
    Transaction.id = 12345

Aggregate with GROUP BY

SELECT
    BUILTIN.DF( Transaction.status ) AS Status,
    COUNT( * ) AS OrderCount,
    SUM( Transaction.total ) AS TotalAmount
FROM
    Transaction
WHERE
    Transaction.type = 'SalesOrd'
GROUP BY
    BUILTIN.DF( Transaction.status )
ORDER BY
    OrderCount DESC

Limiting Results (No LIMIT keyword!)

SELECT * FROM (
    SELECT
        Customer.id,
        Customer.companyname
    FROM
        Customer
    ORDER BY
        Customer.datecreated DESC
)
WHERE ROWNUM <= 10

Security Best Practices

Security is critical when building NetSuite applications. Follow these practices:

1. Always Use Parameterized Queries

Never concatenate user input directly into SQL strings. Use the params array:

// GOOD - Parameterized (safe)
const results = query.runSuiteQL({
    query: 'SELECT * FROM Customer WHERE id = ?',
    params: [customerId]
}).asMappedResults();

// BAD - String concatenation (SQL injection risk!)
const results = query.runSuiteQL({
    query: 'SELECT * FROM Customer WHERE id = ' + customerId
}).asMappedResults();
Why It Matters: If customerId contains 1 OR 1=1, the unsafe version returns ALL customers. Parameterized queries treat the value as data, not SQL code.

2. Never Expose API Keys in Client-Side Code

API keys should be:

3. Be Careful with isOnline Files

When creating files with file.create():

const fileObj = modules.file.create({
    name: 'report.pdf',
    contents: pdfContents,
    folder: folderId,
    fileType: modules.file.Type.PDF,
    isOnline: false  // IMPORTANT: Keep files private!
});

Setting isOnline: true makes the file publicly accessible without authentication. Only use this for intentionally public files.

4. Validate and Sanitize Input

Always validate data from external sources:

// Validate expected data types
if (typeof payload.customerId !== 'number') {
    throw new Error('Invalid customer ID');
}

// Validate against allowed values
const allowedTypes = ['SalesOrd', 'Invoice', 'PurchOrd'];
if (!allowedTypes.includes(payload.transactionType)) {
    throw new Error('Invalid transaction type');
}

5. Use Appropriate Role Permissions

6. Log Sensitive Operations

Use log.audit() for security-relevant events:

modules.log.audit({
    title: 'Sensitive Operation',
    details: `User ${runtime.getCurrentUser().id} exported ${records.length} customer records`
});

Debugging Tips

Effective debugging is essential for SuiteScript development. Here are key techniques:

1. Use the Execution Log

The N/log module writes to NetSuite's Execution Log:

// Different log levels
log.debug({ title: 'Debug Info', details: myVariable });
log.audit({ title: 'Important Event', details: 'User clicked submit' });
log.error({ title: 'Error Occurred', details: e.message });

// Log objects (automatically stringified)
log.debug({ title: 'Query Results', details: records });

// Log governance usage
log.debug({
    title: 'Governance Check',
    details: 'Remaining: ' + runtime.getCurrentScript().getRemainingUsage()
});

To view logs: Go to the Script Deployment record → click View Execution Log

Tip: Set the deployment's Log Level to "Debug" during development. Change to "Error" in production to reduce log noise.

2. Use Browser Developer Tools for Client-Side Code

For JavaScript running in the browser (inside INLINEHTML):

3. Isolate the Problem

When debugging complex queries or operations:

4. Common Debugging Patterns

Log Before and After

log.debug({ title: 'Before Query', details: { query: sql, params: params } });
const results = query.runSuiteQL({ query: sql, params: params }).asMappedResults();
log.debug({ title: 'After Query', details: { count: results.length } });

Wrap in Try/Catch with Logging

try {
    // Your code here
} catch (e) {
    log.error({
        title: 'Error in myFunction',
        details: {
            message: e.message,
            name: e.name,
            stack: e.stack
        }
    });
    throw e;  // Re-throw if needed
}

Conditional Logging

const DEBUG = true;  // Set to false in production

if (DEBUG) {
    log.debug({ title: 'Detailed Debug', details: bigObject });
}

5. Test in Sandbox First

Always test scripts in a Sandbox account before deploying to production:

6. Use the Script Debugger

NetSuite has a built-in debugger for server-side SuiteScript:

  1. On your script record, check Enable Script Debugger
  2. Go to Customization > Scripting > Script Debugger
  3. Trigger your script and step through the code
Note: The Script Debugger has a timeout and only works for server-side code. For client-side JavaScript, use browser developer tools.

Frequently Asked Questions

SuiteQL Questions

Q: Why can't I use LIMIT like in MySQL?

A: SuiteQL is based on Oracle SQL, which doesn't have a LIMIT keyword. Use ROWNUM instead:

-- Get first 10 rows
SELECT * FROM Customer WHERE ROWNUM <= 10

-- For pagination (rows 11-20), wrap in subquery
SELECT * FROM (
    SELECT ROWNUM AS rn, c.* FROM Customer c
) WHERE rn BETWEEN 11 AND 20

Q: How do I find table and column names?

A: Several options:

Q: Why is my query slow?

A: Common causes and fixes:

Q: Why do I get numbers instead of names for some fields?

A: Reference fields (foreign keys) store internal IDs. Use BUILTIN.DF() to get the display value:

-- Returns ID like "42"
SELECT status FROM Transaction

-- Returns name like "Pending Fulfillment"
SELECT BUILTIN.DF(status) AS StatusName FROM Transaction

Q: Can I INSERT, UPDATE, or DELETE with SuiteQL?

A: No. SuiteQL is read-only. To modify data, use the N/record module:

const rec = record.load({ type: 'customer', id: 123 });
rec.setValue({ fieldId: 'phone', value: '555-1234' });
rec.save();

SuiteScript Questions

Q: What's the difference between SuiteScript 1.0 and 2.x?

A: SuiteScript 2.x (current) uses modern JavaScript with AMD modules. Key differences:

Feature SuiteScript 1.0 SuiteScript 2.x
Module System Global functions AMD (define/require)
JavaScript ES3/ES5 ES6+ (with 2.1)
API Style nlapiLoadRecord() record.load()
Status Legacy (avoid) Current (use this)

Q: How do I know if I'm running out of governance?

A: Check remaining units with:

const remaining = runtime.getCurrentScript().getRemainingUsage();
log.debug('Governance remaining', remaining);

// Bail out if running low
if (remaining < 100) {
    throw new Error('Low governance - stopping early');
}

Q: Can I use npm packages in SuiteScript?

A: Not directly. SuiteScript runs in NetSuite's environment, not Node.js. However:

Q: How do I schedule a script to run automatically?

A: Create a Scheduled Script:

  1. Set @NScriptType ScheduledScript in your file
  2. Implement the execute function
  3. Deploy and set a schedule (daily, hourly, etc.) on the deployment record

Tool-Specific Questions

Q: Where are my queries saved?

A: Query history is stored in your browser's localStorage. It persists across sessions but is specific to your browser and device. Clearing browser data will erase history.

Q: Can I share queries with colleagues?

A: Yes! Use the Share button to generate a URL that includes your query. Anyone with access to the tool can open that URL and see your query.

Q: Why do I need an API key for AI features?

A: The AI features use external services (Anthropic Claude or OpenAI). You provide your own API key, which keeps costs transparent and data under your control. Keys are stored in your browser session, not on NetSuite servers.

Printable Cheat Sheet

A condensed reference you can print and keep nearby. (Tip: Use your browser's Print function and select "Print selection" if available.)

SuiteQL & SuiteScript Cheat Sheet

Common Tables

TransactionOrders, invoices, etc.
TransactionLineLine items
CustomerCustomers
VendorVendors
EmployeeEmployees
ItemAll items
AccountGL accounts

Transaction Types

SalesOrdSales Order
InvoiceInvoice
PurchOrdPurchase Order
VendBillVendor Bill
JournalJournal Entry
CustPymtCustomer Payment

N/ Modules

N/querySuiteQL queries
N/recordCRUD operations
N/fileFile Cabinet
N/httpsExternal APIs
N/runtimeScript/user info
N/logLogging

Key Functions

BUILTIN.DF(field)     -- Display value
NVL(field, default)   -- Handle NULL
TO_DATE('2024-01-01', 'YYYY-MM-DD')
TO_CHAR(date, 'YYYY-MM-DD')
SYSDATE               -- Current date
ROWNUM                -- Row limiter

Query Pattern

SELECT
  t.tranid,
  BUILTIN.DF(t.entity) AS Name
FROM Transaction t
WHERE t.type = 'SalesOrd'
  AND t.trandate >= TO_DATE(...)
ORDER BY t.trandate DESC

Governance Costs

query.runSuiteQL()10 units
record.load()5-10 units
file.load()10 units
https.post()10 units
log.*()0 units

Script Types

SuiteletCustom pages/APIs
User EventRecord triggers
ScheduledBackground jobs
Map/ReduceLarge data processing
RESTletREST endpoints
Quick Reminders:

Additional Resources

Official Documentation

SuiteQL Resources

Community Resources

Tools